// -*- c-basic-offset: 2 default-tab-width: 2 indent-tabs-mode: t -*-
// vim: autoindent tabstop=2 noexpandtab shiftwidth=2 softtabstop=2
//===------------ Attributes.cpp
//
//             This file is part of the reactor project
//                Author: R-Core Team
//
//===---------------------------------------------------------------------===//

#include "Array.h"
#include "Const.h"
#include "RAttributes.h"

using namespace R;

#define ATTR_VALUES(attr, names, ...)   \
    names,
const char *DefaultAttributesNames[] = {
    on_attr(ATTR_VALUES, 0)
		"<LastAttr>"
};
#undef ATTR_VALUES


void RAttributes::initAttributesList() {
}

// isKnowAttribute - Test if 'attr_name' is a default attribute.
// Returns index of a known default attribute, else -1.
//
int isAttrById(const char* the_attr_name) {
	for (int idx = 0; idx < NB_Attributes; idx++)
		if(!strcmp(DefaultAttributesNames[idx], the_attr_name))
			return idx;
	return -1;
}

// Gets the default attribute from 'self' object at index 'idx'.
//
Any* getAttrById(Any*self, int idx) {
	R_PARAM(self);
	return Any::CastTo<Vector>(self->attributes)->getElement(idx);
}

// Gets the NO default 'attr' attribute from 'self' object.
//
Any* getAttrByName(Any* self, char* the_attr) {
	R_PARAM(self);
	R_VAR(String*, names);
	R_VAR(Vector*, attrs);
	R_VAR(Vector*, attrs_attrs);
	int idx = 0;

	attrs = Any::CastTo<Vector>(self->attributes);

	if((idx = RAttributes::getIndexByName(attrs, the_attr)) <  0)
		// No such attribute found
		return ConstPool::Null;
	
	return attrs->getElement(idx + NB_Attributes);
}

// Gets the NO default 'attr' attribute from 'self' object.
//
void setAttrByName(Any* self, char* the_attr, Any* value) {
	R_PARAM(self);
	R_PARAM(value);
	R_VAR(String*, names);
	R_VAR(Vector*, attrs);
	R_VAR(Vector*, attrs_attrs);
	int idx;

	attrs = Any::CastTo<Vector>(self->attributes);
	if((names = RAttributes::getNames(attrs)) == ConstPool::Null) { // There are only default attributes set
		// if value is null, useless to create new null attribute
		if(value == ConstPool::Null) return;
		attrs = attrs->enlarge(1);
		attrs->setElement(value, attrs->getLength()-1);
		RAttributes::setNames(attrs, String::doNewSingle(the_attr));
		self->attributes = attrs;
		return;
	}

	if(value != ConstPool::Null) {
		for (int i = 0; i < names->getLength(); i++) {
			if(!strcmp(names->getElement(i), the_attr)) {
				attrs = attrs->shrinkAt(i + NB_Attributes);
				self->attributes = attrs;
				return;
			}
		}
	} else {
		for (int i = 0; i < names->getLength(); i++) {
			if(!strcmp(names->getElement(i), the_attr)) {
				if(value != ConstPool::Null) {
					attrs->setElement(value, i + NB_Attributes);
					return;
				}
			}
		}
	}

	// Now we are sure that attribute does not exist and we have to create it

	// if value is null, leave without doing anything
	if(value == ConstPool::Null) return;
	// first enlarge attributes to put the new one
	attrs = attrs->enlarge(1);
	// put attribute at the end of the vector
	attrs->setElement(value, attrs->getLength()-1);
	// set attribute's name
	names = RAttributes::getNames(attrs);
	names->setElement(the_attr, names->getLength()-1);
	self->attributes = attrs;
}

void createAndSetAttrByName(Any* self, char* name, Any* value){
	R_PARAM(self);
	R_PARAM(value);
	R_VAR(Vector*, attr);
	R_VAR(String*, attr_name);

	if(value == ConstPool::Null) return;

	attr_name = String::doNewSingle(name);
	attr = Vector::doNew(NB_Attributes+1);
	attr->attributes = Vector::doNew(NB_Attributes);
	Any::CastTo<Vector>(attr->attributes)->setElement(attr_name, ATTR_NAMES);
	attr->setElement(value, NB_Attributes);
	self->attributes = attr;
}

void RAttributes::setAttrById(Any* self, int idx, Any* value) {
	R_PARAM(self);
	R_PARAM(value);
	Any::CastTo<Vector>(self->attributes)->setElement(value, idx);
}

void RAttributes::createAndSetAttrById(Any* self, int idx, Any* value) {
	R_PARAM(self);
	R_PARAM(value);
	// There are no attributes, we don't need to create them for null value.
	if(value == ConstPool::Null) return;
	self->attributes = Vector::doNew(NB_Attributes);
	setAttrById(self, idx, value);
}

Any* Any::getAttr(char* the_attr) {
	asSelf(Any);
	int idx;

	if(!self->attributes) return NULL; 	// self has no attributes at all
	if((idx = isAttrById(the_attr)) >= 0){
		return getAttrById(self, idx);
	}
	if(!self->attributes->attributes) return NULL; 	// self doesn't have no-default attributes
	return getAttrByName(self, the_attr);
}

// Get attr from object self.
//
Any* Any::setAttr(char* the_attr, Any* value) {
	asSelf(Any);
	R_PARAM(value);
	int idx=0;

	idx = isAttrById(the_attr);

	// No default attribute case
	if (idx < 0) {
		if(!self->attributes) {
			createAndSetAttrByName(self, the_attr, value);
			return value;
		}
		// Have to check if the attribute exists or if we have to create it.
		setAttrByName(self, the_attr, value);
		return value;
	}

	// We are talking about a default attribute
	if(!self->attributes) // There are no attributes at all
		RAttributes::createAndSetAttrById(self, idx, value);
	else
		RAttributes::setAttrById(self, idx, value);

	return value;
}

//	TODO Change recursive implementation
void Any::setAttributes(Any* attribute) {
	asSelf(Any);
	R_PARAM(attribute);
	R_VAR(Vector*, attr);

	if(attribute == ConstPool::Null) { self->attributes = ConstPool::Null; return; }

	attr = static_cast<Vector*>(static_cast<Vector*>(attribute)->duplicate(true));

	self->attributes = attr;
}

Any* Any::getAttributes() {
	asSelf(Any);
	return self->attributes;
}

Any* checkAttributeById(Any* left_op, int idx, Any* right_op) {
	R_VAR(Array*, right_op_array);
	R_VAR(Array*, left_op_array);
	R_VAR(Int *, right_op_int);
	R_PARAM(left_op);
	R_PARAM(right_op);
	int product = 1;
	int val = 0;

	right_op_array = static_cast<Array*>(right_op);
	left_op_array = static_cast<Array*>(left_op);

	switch (idx) {

		case ATTR_NAMES:
			// case names attribute
			if(!Any::isOfType<String>(right_op)) {
				nyi_fatal();
				rerror_on(true, "Unimplemented, convert Array values to String"); /* right_op = right_op->to_string(); */
			}

			rerror_on((right_op_array->length > left_op_array->length),
					"'names' attribute [%d] must be the same length as the vector [%d]",
						right_op_array->length, left_op_array->length);

			if(right_op_array->length < left_op_array->length) {
				nyi_fatal();
				rerror_on(true, "Unimplemented, fill attribute value to vector size with NA chars");
			}
			break;

		case ATTR_CLASS:
			// case class attribute
			rerror_on(!Any::isOfType<String>(right_op), "attempt to set invalid 'class' attribute");
			break;

		case ATTR_DIM:
			//case dim attribute
			if(!Any::isOfType<Int>(right_op)) {
				nyi_fatal();
				rerror_on(true, "not yet implemented, convert Array values to Int");
				// TODO in addition warning, say if NAs were introduced by coercion
			}
			right_op_int = static_cast<Int*>(right_op);
			for(int i = 0; i<right_op_int->length; i++) {
				rerror_on(((val = right_op_int->getElement(i)) == ConstPool::NA_INT),
						"the dims contain missing values");
				product *= val;
			}
			rerror_on((left_op_array->length != product), "dims [product %d] do not match the length of object [%d]",
						product, left_op_array->length);
			break;

		default:
			rerror_on(true, "Attribute by ID not found");
			break;

	}
	return right_op;
}

Any* RAttributes::checkSetAttr(Any* left_op, Any* attr, Any* right_op) {
	R_PARAM(left_op);
	R_PARAM(attr);
	R_PARAM(right_op);
	char* char_attr = NULL;
	int idx = 0;

	// Check for attribute name correctness
	rerror_on((attr == ConstPool::Null ||
			!Any::isOfType<String>(attr) ||
			(Any::isOfType<String>(attr) && static_cast<String*>(attr)->length < 1)),
			"'name' must be non-null character string");

	char_attr = static_cast<String*>(attr)->getElement(0);
	rerror_on((!strlen(char_attr)), "attempt to use zero-length variable name");

	// Check for value correctness
	rerror_on((!Any::isOfType<Array>(right_op)), "attribute value must be an array !");

	if((idx = isAttrById(char_attr)) < 0)
		return right_op;

	return checkAttributeById(left_op, idx, right_op);
}

Any* RAttributes::checkSetAttributes(Any* left_op, Any* right_op) {
	R_PARAM(left_op);
	R_PARAM(right_op);
	R_VAR(Vector*, new_attr);

	if(right_op == ConstPool::Null) return ConstPool::Null;

	rerror_on(!Any::isOfType<Vector>(right_op), "attributes must be a list or NULL");
	new_attr = static_cast<Vector*>(right_op);

	/* Just check if there are extra attributes which must be named */
	rerror_on((new_attr->length > NB_Attributes) &&
			(new_attr->attributes == ConstPool::Null), "attributes must be named");

	for(int i = 0; i < NB_Attributes; i++) {
		new_attr->setElement(checkAttributeById(left_op, i, new_attr->getElement(i)), i);
	}

	return new_attr;
}

Any* RAttributes::checkGetAttr(Any* left_op, Any* attr_name) {
	R_PARAM(left_op);
	R_PARAM(attr_name);

	rerror_on(((attr_name == ConstPool::Null) || !Any::isOfType<String>(attr_name)),
			"'which' must be of mode character");

	rerror_on((static_cast<String*>(attr_name)->length != 1),
			"exactly one attribute 'which' must be given");

	if(left_op == ConstPool::Null)
		return ConstPool::Null;

	return left_op;
}

String* RAttributes::getNames(Any* self) {
	R_PARAM(self);
	R_VAR(Vector*, tmp);
	if((tmp = static_cast<Vector*>(self->attributes)) != ConstPool::Null)
		return static_cast<String*>(tmp->content[ATTR_NAMES]);
	return NULL;
}

void RAttributes::setNames(Any* self, Any* names) {
	R_PARAM(self);
	R_PARAM(names);

	if(self->attributes == ConstPool::Null)
		RAttributes::createAndSetAttrById(self, ATTR_NAMES, names);
	else
		RAttributes::setAttrById(self->attributes, ATTR_NAMES, names);
}

bool RAttributes::isInNames(Any* self, char* name) {
	R_PARAM(self);
	R_VAR(String*, names);

	if(!(names = getNames(self))) return false;

	for(int i = 0; i < names->length; i++) {
		if(!strcmp(names->content[i], name)) {
			return true;
		}
	}
	return false;
}

int RAttributes::getIndexByName(Any* self, char* key) {
		R_PARAM(self);
		R_VAR(String*, names);

		if(!(names = getNames(self))) return -1;

		for(int i = 0; i < names->length; i++) {
			if(!strcmp(names->content[i], key)) {
				return i;
			}
		}
		return -1;
}

void RAttributes::fillNamesWith(Any* self, char* key) {
	R_PARAM(self);
	R_VAR(String*, names);
	int len = 0;

	len = static_cast<Array*>(self)->length;
	names = String::doNew(len);
	for(int i = 0; i < len; i++) {
		names->content[i] = (char*)"";
	}
	RAttributes::setNames(self, names);
}

char* RAttributes::getNameAt(Any*self, int idx, bool copy) {
	R_PARAM(self);
	R_VAR(String*, names);

	names = getNames(self);

	if(idx > names->length) {
		return (char*)ConstPool::NA_STRING;
	}

	if(copy)
		return strdup(names->content[idx]);

	return names->content[idx];
}
